#include "wfnet.h"

#include "arc.h"
#include "expression.h"
#include "journal.h"
#include "place.h"
#include "transition.h"
#include "utils/bindinggenerator.h"
#include "utils/dynamictypemanager.h"
#include "utils/expressionvalidator.h"
#include "utils/netrunnable.h"
#include "utils/netrunner.h"

#include <node.h>
#include <QDebug>
using namespace core;
using namespace wfnet;

WfNet::WfNet(QObject *parent) : QObject(parent), INet(),
    m_speed(0),
    m_isLocked(false),
    m_globalClock(0),
    m_currentBinding(),
    m_graph(false), // do not erase node when disconnected
    m_places(),
    m_transitions(),
    m_arcs(),
    m_expressions(),
    m_colorSets(),
    m_variables(),
    m_enabledTransitions(),
    m_initialMarking(),
    m_journal(0)
{

}

WfNet::~WfNet()
{

}

void WfNet::run(int repetitions)
{
    if (repetitions > 0)
    {
        NetRunner::getInstance()->runNet(this, repetitions);
    }
}

void WfNet::pause()
{
    NetRunner::getInstance()->pauseNet(this);
}

void WfNet::stop()
{
    NetRunner::getInstance()->stopNet(this);
}

void WfNet::stepForward()
{

}

void WfNet::stepBackward()
{

}

void WfNet::setSimulationSpeed(uint speed)
{
    m_speed = speed;
}

bool WfNet::isLocked() const
{
    return m_isLocked;
}

IPlace* WfNet::addPlace(IColorSet *colorSet, QString name, nid id)
{
    Q_ASSERT(colorSet != 0);

    nid _id = id.isNull() ? nid::createUuid() : id;

    Place* place = new Place(this, _id, name, colorSet);
    m_places[place->getId()] = place;

    return place;
}

ITransition* WfNet::addTransition(QString name, nid id)
{
    nid _id = id.isNull() ? nid::createUuid() : id;

    Transition* transition = new Transition(this, _id, name);
    m_transitions[transition->getId()] = transition;

    return transition;
}

IExpression* WfNet::addExpression(const Expr& expr,
    const TypeName& context, nid id, bool timed)
{
    Expression* expression = 0;

    ExpressionValidator* validator = ExpressionValidator::getInstance();
    if (validator->validate(expr, context, timed, this))
    {
        nid _id = id.isNull() ? nid::createUuid() : id;
        expression = new Expression(this, _id,
            validator->getSystemExpression(),
            validator->getReturnType(),
            validator->getFoundVariables(),
            validator->getSystemVariables(),
            validator->getExprType());
        m_expressions[expression->getId()] = expression;
    }

    return expression;
}

void WfNet::removePlace(IPlace *place)
{
    disconnectAll(place);
    Place* _place = m_places[place->getId()];
    m_places.remove(_place->getId());

    m_graph.removeNode(_place->m_node);
    delete _place->m_node;

    delete _place;
}

void WfNet::removeTransition(ITransition *transition)
{
    disconnectAll(transition);
    Transition* _transition = m_transitions[transition->getId()];
    m_transitions.remove(_transition->getId());

    m_graph.removeNode(_transition->m_node);
    delete _transition->m_node;

    delete _transition;
}

void WfNet::removeExpression(IExpression *expression)
{
    Expression* _expression = m_expressions[expression->getId()];
    m_expressions.remove(_expression->getId());

    delete _expression;
}

IArc* WfNet::connect(IPlace *source, ITransition *destination)
{
    Place* _source = m_places[source->getId()];
    Transition* _destination = m_transitions[destination->getId()];

    Node* sourceNode = _source->m_node == 0 ?
        Node::create(_source) : _source->m_node;
    Node* destinationNode = _destination->m_node == 0 ?
        Node::create(_destination) : _destination->m_node;

    Arc* arc = new Arc(this, nid::createUuid(), _source,
        _destination, Arc::PlaceToTransition);

    m_graph.connect(sourceNode, destinationNode, arc);

    m_arcs[arc->getId()] = arc;

    return arc;
}

IArc* WfNet::connect(ITransition *source, IPlace *destination)
{
    Transition* _source = m_transitions[source->getId()];
    Place* _destination = m_places[destination->getId()];

    Node* sourceNode = _source->m_node == 0 ?
        Node::create(_source) : _source->m_node;
    Node* destinationNode = _destination->m_node == 0 ?
        Node::create(_destination) : _destination->m_node;

    Arc* arc = new Arc(this, nid::createUuid(), _destination,
        _source, Arc::TransitionToPlace);

    m_graph.connect(sourceNode, destinationNode, arc);

    m_arcs[arc->getId()] = arc;

    return arc;
}

void WfNet::disconnect(IArc *arc)
{
    const nid id = arc->getId();
    Arc* _arc =  m_arcs[id];

    Node* placeNode = _arc->getPlace()->m_node;
    Node* transitionNode = _arc->getTransition()->m_node;

    if (_arc->getConnectionType() == Arc::PlaceToTransition)
    {
        m_graph.disconnect(placeNode, transitionNode);
    }
    else
    {
        m_graph.disconnect(transitionNode, placeNode);
    }

    m_arcs.remove(id);
    delete _arc;
}

void WfNet::disconnectAll(IPlace *place)
{
    Place* _place = m_places[place->getId()];

    const QSet<const ITransition* > iTransitions = _place->getInputTransitions();
    const QSet<const ITransition* > oTransitions = _place->getOutputTransitions();

    foreach (const ITransition* transition, iTransitions)
    {
        Transition* _transition = m_transitions[transition->getId()];

        Edge* edge = m_graph.getEdge(_transition->m_node, _place->m_node);
        const Arc* arc = dynamic_cast<const Arc* >(edge->getData());

        m_graph.disconnect(_transition->m_node, _place->m_node);

        m_arcs.remove(arc->getId());

        delete arc;
    }

    foreach (const ITransition* transition, oTransitions)
    {
        Transition* _transition = m_transitions[transition->getId()];

        Edge* edge = m_graph.getEdge(_place->m_node, _transition->m_node);
        const Arc* arc = dynamic_cast<const Arc* >(edge->getData());

        m_graph.disconnect(_place->m_node, _transition->m_node);

        m_arcs.remove(arc->getId());

        delete arc;
    }
}

void WfNet::disconnectAll(ITransition *transition)
{
    Transition* _transition = m_transitions[transition->getId()];

    const QSet<const IPlace* > iPlaces = _transition->getInputPlaces();
    const QSet<const IPlace* > oPlaces = _transition->getOutputPlaces();

    foreach (const IPlace* place, iPlaces)
    {
        Place* _place = m_places[place->getId()];

        Edge* edge = m_graph.getEdge(_place->m_node, _transition->m_node);
        const Arc* arc = dynamic_cast<const Arc* >(edge->getData());

        m_graph.disconnect(_place->m_node, _transition->m_node);

        m_arcs.remove(arc->getId());

        delete arc;
    }

    foreach (const IPlace* place, oPlaces)
    {
        Place* _place = m_places[place->getId()];

        Edge* edge = m_graph.getEdge(_transition->m_node, _place->m_node);
        const Arc* arc = dynamic_cast<const Arc* >(edge->getData());

        m_graph.disconnect(_transition->m_node, _place->m_node);

        m_arcs.remove(arc->getId());

        delete arc;
    }
}

IPlace* WfNet::place(nid id) const
{
    IPlace* ret = 0;

    if (m_places.contains(id))
    {
        ret = m_places[id];
    }

    return ret;
}

ITransition* WfNet::transition(nid id) const
{
    ITransition* ret = 0;

    if (m_transitions.contains(id))
    {
        ret = m_transitions[id];
    }

    return ret;
}

const DirectedGraph& WfNet::getGraph() const
{
    return m_graph;
}

QSet<ColorSetName> WfNet::registeredColorSets() const
{
    return m_colorSets;
}

QSet<VariableName> WfNet::registeredVariables() const
{
    return m_variables;
}

ColorSetName WfNet::variableType(const VariableName& name) const
{
    const Type* type = DynamicTypeManager::getInstance()->getVariable(
        name, this);

    return type != 0 ? type->name() : Type::INVALID_NAME;
}

bool WfNet::isVariableTimed(const VariableName& name) const
{
    const ColorSet* color = dynamic_cast<const ColorSet*>(
        DynamicTypeManager::getInstance()->getVariable(name, this));

    Q_ASSERT(color != 0);

    return color->isTimed();
}

void WfNet::initialMarkingSet()
{
    saveInitialMarking();
}

void WfNet::resetInitialMarking()
{
    deleteInitialMarking();
}

BatchJournal* WfNet::journal() const
{
    return m_journal;
}

QList<nid> WfNet::places() const
{
    return m_places.keys();
}

void WfNet::registerColorSet(const ColorSetName& name)
{
    m_colorSets.insert(name);
}

void WfNet::registerVariable(const VariableName& name)
{
    m_variables.insert(name);
}

void WfNet::unregisterColorSet(const ColorSetName& name)
{
    m_colorSets.remove(name);
}

void WfNet::unregisterVariable(const VariableName& name)
{
    m_variables.remove(name);
}

void WfNet::determineEnabledTransitions()
{
    //qDebug() << "det";
    foreach (Transition* transition, m_transitions)
    {
        QSet<const Arc* > toBeCheckedArcs;

        BasicTime proposedGlobalClock;
        Binding binding;

        const bool isBindable = BindingGenerator::getInstance()->bind(
            binding, m_globalClock, proposedGlobalClock,
            transition->getInputArcs(m_graph),
            transition->getOutputArcs(m_graph),
            transition, toBeCheckedArcs);
        //qDebug() << transition->name() << isBindable << binding.size();

        if (isBindable)
        {
            bool add = true;

            foreach (const Arc* arc, toBeCheckedArcs)
            {
                IMultiset* imt = arc->eval(m_globalClock);

                if (imt->isTimed())
                {
                    TimedMultiset* emt = dynamic_cast<TimedMultiset *>(
                        imt);

                    const TimedMultiset* pmt =
                        dynamic_cast<const TimedMultiset* >(
                            arc->getPlace()->getTokens());

                    Q_ASSERT(pmt != 0 && emt != 0);

                    const TimedMultiset _emt = *emt + transition->getDelay();

                    if (!(_emt <= *pmt))
                    {
                        add = false;
                        break;
                    }

                    delete emt;
                }
                else
                {
                    Multiset* emt = dynamic_cast<Multiset *>(
                        imt);

                    const Multiset* pmt =
                        dynamic_cast<const Multiset* >(
                            arc->getPlace()->getTokens());

                    Q_ASSERT(pmt != 0 && emt != 0);

                    if (!(*emt <= *pmt))
                    {
                        add = false;
                        break;
                    }

                    delete emt;
                }
            }

            if (add && transition->check())
            {
                m_enabledTransitions << transition;
                m_currentBinding[transition] = binding;
            }
        }        
    }
}

void WfNet::fireRandomTransition()
{
    if (m_enabledTransitions.size() > 0)
    {
        const int N = m_enabledTransitions.size() - 1;

        const int index = (qrand() % (N - 0 + 1)) + 0;

        Transition* transition = m_enabledTransitions.toList().at(index);

        QSet<const Arc* > oArcs =  transition->getOutputArcs(m_graph);

        Q_ASSERT(m_currentBinding.contains(transition)); // can't happen

        foreach (const Arc* arc, oArcs)
        {
            Expression* expression = dynamic_cast<Expression* >(
                arc->getExpression());
            expression->setBinding(m_currentBinding[transition]);
        }

        //qDebug() << "tr" << transition->name();
        transition->fire(m_globalClock);

        m_journal->transitionExecuted(transition->getId());
    }
    else
    {
        m_isLocked = true;
    }

    m_enabledTransitions.clear();
    m_currentBinding.clear();

    m_journal->incrementSteps();
}

void WfNet::saveInitialMarking()
{
    foreach (Place* place, m_places)
    {
        m_initialMarking[place] = place->getTokens()->toValue();
    }
}

void WfNet::deleteInitialMarking()
{
    m_initialMarking.clear();
}

void WfNet::restoreInitialMarking()
{
    foreach (Place* place, m_places)
    {
        if (m_initialMarking.contains(place))
        {
            place->clearTokens();

            const ColorSet* colorset = dynamic_cast<const ColorSet*>(
                place->getColorSet());

            if (colorset->isTimed())
            {
                TimedMultiset ms(colorset->name(), m_initialMarking[place]);

                place->putTokens(&ms);
            }
            else
            {
                Multiset ms(colorset->name(), m_initialMarking[place]);

                place->putTokens(&ms);
            }
        }
    }

    m_isLocked = false;
}

void WfNet::createNewJournal()
{
    if (m_journal != 0)
    {
        delete m_journal;
    }

    m_journal = new BatchJournal(this);
}

void WfNet::emitFinished()
{
    emit finished();
}
